package gbench.sandbox.tuple;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.*;

import org.junit.jupiter.api.Test;

public class JunitM1{
    
    public static class Tuple2<T,U>{
        public Tuple2(final T t,final U u) {
            _1 = t;
            _2 = u;
        }
        public final T _1;
        public final U _2;
        
        /**
         * 拉链函数
         * @param <T>
         * @param <U>
         * @param tt
         * @param uu
         * @return [(t1,u1),(t2,u2),...]
         */
        public static <T,U> Stream<Tuple2<T,U>> zip(final T[] tt,final U[] uu){
            return stm(Math.max(tt.length, uu.length)).map(i->new Tuple2<>(tt[i%tt.length],uu[i%uu.length]));
        }
    }
    
    public static Stream<Integer> stm(){
        return Stream.iterate(0, i->i+1);
    }
    
    public static Stream<Integer> stm(final Number size){
        return Stream.iterate(0, i->i+1).limit(size.longValue());
    }
    
    @SafeVarargs
    public static <T> T[] A(T  ...ts) {
        return ts;
    }
    
    @SafeVarargs
    public static <T> List<T> L(T  ...ts) {
        return Arrays.asList(ts);
    }
    
    /**
     * 向量点积
     * @param <T>
     * @param <U>
     * @param tt
     * @param uu
     * @return 点积结果
     */
    public static <T extends Number,U extends Number> Double dot(final T[] tt,final U[] uu){
         return Tuple2.zip(tt, uu).map(p->p._1.doubleValue()*p._2.doubleValue()).reduce((a,b)->a+b).get();
    }
    
    /**
     * 向量加法
     * @param <T>
     * @param <U>
     * @param tt
     * @param uu
     * @return 加法结果
     */
    public static <T extends Number,U extends Number> Double[] add(final T[] tt,final U[] uu){
         return Tuple2.zip(tt, uu).map(p->p._1.doubleValue()+p._2.doubleValue()).toArray(Double[]::new);
    }
    
    /**
     * 向量减法
     * @param <T>
     * @param <U>
     * @param tt
     * @param uu
     * @return 减法结果
     */
    public static <T extends Number,U extends Number> Double[] sub(final T[] tt,final U[] uu){
         return Tuple2.zip(tt, uu).map(p->p._1.doubleValue()-p._2.doubleValue()).toArray(Double[]::new);
    }
    
    /**
     * 向量乘法
     * @param <T>
     * @param <U>
     * @param tt
     * @param uu
     * @return 乘法结果
     */
    public static <T extends Number,U extends Number> Double[] mul(final T[] tt,final U[] uu){
         return Tuple2.zip(tt, uu).map(p->p._1.doubleValue()*p._2.doubleValue()).toArray(Double[]::new);
    }
    
    /**
     * 向量乘法
     * @param <T>
     * @param <U>
     * @param tt
     * @param uu
     * @return 乘法结果
     */
    public static <T extends Number,U extends Number> Double[] mul(final T t ,final U[] uu){
         return Tuple2.zip(A(t), uu).map(p->p._1.doubleValue()*p._2.doubleValue()).toArray(Double[]::new);
    }
    /**
     * 向量除法
     * @param <T>
     * @param <U>
     * @param tt
     * @param uu
     * @return 除法结果
     */
    public static <T extends Number,U extends Number> Double[] div(final T[] tt,final U[] uu){
         return Tuple2.zip(tt, uu).map(p->p._1.doubleValue()/p._2.doubleValue()).toArray(Double[]::new);
    }
    
    
    
    /**
     * 数组生成函数
     * @param <T>
     * @param clazz
     * @return T数组
     */
    @SuppressWarnings("unchecked")
    public static <T> IntFunction<T[]> arr(final Class<T> clazz){
        return n->(T[]) Array.newInstance(clazz, n);
    }
    
   /**
    * 获取矩阵的 形状(行数,列数)
    * @param <T>
    * @param tt
    * @return (行数,列数)
    */
    public static <T> Tuple2<Integer,Integer> shape(final T[][] tt){
        return new Tuple2<>(tt.length,Stream.of(tt).filter(e->e!=null).collect(Collectors.summarizingInt(e->e.length)).getMax());
    }
    
    
    @SuppressWarnings("unchecked")
    public static <T> T[][] transpose(final T[][] tt){
        final var shape = shape(tt);
        return stm(shape._2).map(i->stm(shape._1).map(j->tt[j][i]).toArray(arr(tt.getClass().getComponentType().getComponentType())))
                .toArray(arr((Class<T[]>)tt.getClass().getComponentType()));
    }
    
   /**
    * 矩阵乘法
    * @param <T>
    * @param <U>
    * @param tt 左乘矩阵
    * @param uu 右乘矩阵
    * @return 矩阵乘法
    */
    public static <T extends Number,U extends Number> Double[][] mmult( final T[][] tt, final U[][] uu){
        final var shape = new Tuple2<>(shape(tt)._1,shape(uu)._2);
        final var ss = transpose(uu);
        return stm(shape._1).map(i->stm(shape._2).map(j->dot(tt[i],ss[j])).toArray(Double[]::new)).toArray(Double[][]::new);
    }
    
    /**
     * 矩阵格式化
     * @param <T>
     * @param tt
     * @return 矩阵格式化
     */
    public static <T> String fmt(final T[][] tt) {
        return Stream.of(tt).map(t->Stream.of(t).map(p->p+"").collect(Collectors.joining("\t"))).collect(Collectors.joining("\n"));
    }
    
    /**
     * 
     * @param <T>
     * @param alphas
     * @return
     */
    public static <T extends Number> Double[][] schmidt(List<T[]> alphas) {
        final var betas = new LinkedList<Double[]>();
        final var eps = 1e-10;
        final Predicate<Number[]> is_zero = aa -> Stream.of(aa).allMatch(e -> Math.abs(e.doubleValue()) < eps);

        alphas.forEach(alpha -> {
            final var b = betas.stream().map(beta -> mul(dot(alpha, beta) / dot(beta, beta), beta))
                    .reduce(JunitM1::add).map(e -> sub(alpha, e))
                    .orElse(Stream.of(alpha).map(e -> e.doubleValue()).toArray(Double[]::new));
            if (!is_zero.test(b))
                betas.add(b);
        });

        return betas.toArray(Double[][]::new);
    }

    @Test
    public void foo() {
        final var aa = A(3,4);
        System.out.println(dot(aa,aa));
        final var bb = A(aa,aa);
        System.out.println(fmt(bb));
        System.out.println("-------------------------");
        System.out.println(fmt(transpose(bb)));
        
        System.out.println(fmt(mmult(bb,bb)));
        
        System.out.println("schmidt:");
        System.out.println(fmt(schmidt(L(A(1,2,3,4),A(2,3,4,5)))));
    }
}